﻿using System;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using FarseerPhysics.Common;
using FarseerPhysics.SamplesFramework;
using Flatland.GeneticProgramming;
using Flatland.GeneticProgramming.CoreTypes;
using Flatland.Geomerty;
using Flatland.Utilities;
using Microsoft.Xna.Framework.Graphics;
using Color = Microsoft.Xna.Framework.Color;

namespace Flatland.GeneBank {
    [Serializable]
    public class Recognizer : GenoType, IExpression<double> {

        //The bitmaps we want to recognize
        private static Bitmap[] _bitMaps = null;
        private static Bitmap _woman = null;
        private static Bitmap _circle = null;

        //Set all bit maps
        void SetupTrainingData() {
            _bitMaps = new Bitmap[Agent.MAX_VERTICES];
            _woman = WomanBitMap();
            _circle = CircleBitMap();
            for (int i = 0; i < Agent.MAX_VERTICES; i++) {
                Vertices verts = Shaper.FlatlanderShape(3 + i, Utils.DegreeToRadian(Agent.DEFAULT_ANGLE));
                for (int j = 0; j < verts.Count; j++) {
                    verts[j] = ConvertUnits.ToSimUnits(verts[j] * Agent.DEFAULT_SIZE); //1 for agent size
                }
                string path = GP.TEMP_FOLDER + @"\texture" + i + ".png";
                Stream stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
                if (Program.FlatlandWorld == null)
                    throw new Exception(GetType().Name + " must be evaluated with a valid Flatland environment.");
                Texture2D texture = Program.FlatlandWorld.Assets.TextureFromVertices(verts, MaterialType.Dots, Color.DarkGray, 1f);
                texture.SaveAsPng(stream, texture.Width, texture.Height);
                stream.Close();
                _bitMaps[i] = new Bitmap(path);
            }
        }

        //Get circle bitmap
        private Bitmap WomanBitMap() {
            string p = GP.TEMP_FOLDER + @"\woman_texture.png";
            Stream s = new FileStream(p, FileMode.Create, FileAccess.Write, FileShare.None);
            Texture2D woman = Program.FlatlandWorld.Assets.TextureFromVertices(new Agent().WomanVerts(0.01f), MaterialType.Blank, Color.DarkBlue, 1f);
            woman.SaveAsPng(s, woman.Width / 2, woman.Height);
            s.Close();
            return new Bitmap(p);
        }

        //Get woman bitmap
        private Bitmap CircleBitMap() {
            string p = GP.TEMP_FOLDER + @"\circle_texture.png";
            Stream s = new FileStream(p, FileMode.Create, FileAccess.Write, FileShare.None);
            Texture2D circle = Program.FlatlandWorld.Assets.CircleTexture(ConvertUnits.ToSimUnits(new Agent().Size), MaterialType.Dots, Color.DarkBlue, 1f);
            circle.SaveAsPng(s, circle.Width, circle.Height);
            s.Close();
            return new Bitmap(p);
        }

        //Set variable according to the bitmaps. True if a color is found
        void SetVars(Bitmap bm) {
            for (int i = 0; i < cols.Count(); i++) {
                cols[i] = 0;
            }
            for (int i = 0; i < bm.Width; i++) {
                for (int j = 0; j < bm.Height; j++) {
                    if (bm.GetPixel(i, j).A != 0 || bm.GetPixel(i, j).R != 0 || bm.GetPixel(i, j).G != 0 || bm.GetPixel(i, j).B != 0){
                        cols[i] = j;
                        break; 
                    }
                }
            }
        }

        //The program's guess 
       // private int guess;

        public override double Fitness() {
            if (_bitMaps == null)
                SetupTrainingData();

            double precision = 0;

            //SetVars(_bitMaps[0]);
            //Execute();
            //precision += Math.Abs(3 - guess);
            double dguess = 0;

            int guess = 0;

           //Check guesses for all men
           for (int i = 0; i < Agent.MAX_VERTICES; i++) {
               SetVars(_bitMaps[i]);
               dguess = (double)Execute();
               guess = (int) dguess;
               precision += Math.Abs((3 + i) - guess);
           }
           
           //For women
           //SetVars(_woman);
           //dguess = (double)Execute();
           //guess = (int)dguess;
           //precision += Math.Abs(2 - guess);
           
           ////For circle
           //SetVars(_circle);
           //precision += 0; //Let's just say 

            return GP.DEFAULT_FITNESS_GOAL - precision;
        }

        public override object Clone() {
            return new Recognizer();
        }

        protected override void Act(ref string msg) {
            Execute();
        }

        [Primitive]
        double[] cols = new double[101];

        [Primitive]
        public double Add(double a, double b) { return a + b; }

        [Primitive]
        public double Sub(double x, double y) { return x - y; }

        [Primitive]
        public double Mul(double x, double y) { return x * y; }

        [Primitive]
        public double Mod(double x, double y) { return Math.Abs(y - 0) < 0.01 ? x : x % y; }

        [Primitive]
        public double Div(double x, double y) { return Math.Abs(y - 0) < 0.01 ? 1 : x / y; }

        [Primitive]
        public double Abs(double x, double y) { return Math.Abs(x - y); }

        [Primitive]
        public double Sin(double x) { return Math.Sin(x); }

        [Primitive]
        public double Cos(double x) { return Math.Cos(x); }

        [Primitive]
        public double Tan(double x) { return Math.Tan(x); }

        [Primitive]
        public double Pow(double x, double y) { return Math.Pow(x, y); }

        [Primitive]
        public double Inc(double x) { return x + 1; }

        [Primitive]
        public double Dec(double x) { return x - 1; }

        [Primitive]
        public double Min(double x, double y) { return Math.Min(x, y); }

        [Primitive]
        public double Max(double x, double y) { return Math.Max(x, y); }

        [Primitive]
        public double Sqr(double x) { return Math.Sqrt(x); }

        [Primitive]
        public double PI() {
            return Math.PI;
        }

        //2.71828182845905
        [Primitive]
        public double E() {
            return Math.E;
        }

        //[Primitive]
        //public int col(int i)
        //{
        //    return cols[Math.Abs(i % cols.Length)];
        //}

        public Recognizer(SerializationInfo info, StreamingContext context) { }
        public Recognizer() { }

    }
}
